---
title: Configure GraphQL Subscription Support
subtitle: Enable clients to receive real-time updates
description: Configure your router to support GraphQL subscriptions, enabling clients to receive real-time updates via WebSocket or HTTP callbacks.
minVersion: Router v1.22.0
---

<PlanRequired plans={["Free", "Developer", "Standard", "Enterprise"]}>

Rate limits apply on the Free plan.
Subscription pricing applies on Developer and Standard plans.
Developer and Standard plans require Router v2.6.0 or later.

</PlanRequired>

Learn how to configure the router to enable GraphQL subscriptions.

## Prerequisites

Before you add `Subscription` fields to your subgraphs, do all of the following in the order shown to prevent schema composition errors:

1. Update your router instances to version `1.22.0` or later. [Download the latest version.](/graphos/routing/get-started)
    - Previous versions of the router don't support subscription operations.
1. Make sure your router is [connected to a GraphOS Enterprise organization](/router/enterprise-features/#enabling-enterprise-features).
    - Subscription support is an Enterprise feature of self-hosted routers.
1. **If you compose your router's supergraph schema with GraphOS** (instead of with the Rover CLI), [update your build pipeline](/graphos/graphs/updating#2-update-your-build-pipeline) to use Apollo Federation 2.4 or later.
    - Previous versions of Apollo Federation don't support subscription operations.

1. Modify your subgraph schemas to use Apollo Federation 2.4 or later:
    ```graphql title="stocks.graphql"
    extend schema
    @link(url: "https://specs.apollo.dev/federation/v2.4", #highlight-line
          import: ["@key", "@shareable"])

    type Subscription {
      stockPricesChanged: [Stock!]!
    }
    ```

    - You can skip modifying subgraph schemas that don't define any `Subscription` fields.

1. If you're using Apollo Server to implement subgraphs, [update your Apollo Server instances to version 4 or later](/apollo-server/migration).
    - Follow the Apollo Server 4 [guide for enabling subscriptions](/apollo-server/data/subscriptions#enabling-subscriptions).
    - Update [`@apollo/subgraph`](/apollo-server/using-federation/apollo-subgraph-setup/#1-install-and-import-apollosubgraph).

After you complete these prerequisites, you can safely [configure your router](#router-setup) for subscriptions.

## Router setup

After completing all [prerequisites](#prerequisites), in your router's [YAML config file](/router/configuration/overview/#yaml-config-file), you configure how the router communicates with each of your subgraphs when executing GraphQL subscriptions.

When communicating with subgraphs, the router will use routing URLs defined in the supergraph or, if specified, the router YAML config. The router supports two popular [WebSocket protocols](#websocket-setup) for subscriptions, and it also provides support for an [HTTP-callback-based protocol](#http-callback-setup). Your router must use whichever protocol is expected by each subgraph.

<Note>
Use `wss://` (WebSocket Secure) instead of `ws://` to encrypt data transmission between the router and subgraphs, providing similar security benefits as using `https://` over `http://`.
</Note>

### WebSocket setup

Here's an example router configuration snippet that sets up subgraph subscriptions over WebSocket:

```yaml title="router.yaml"
subscription:
  enabled: true
  mode:
    passthrough:
      all: # The router uses these subscription settings UNLESS overridden per-subgraph
        path: /subscriptions # The absolute URL path to use for subgraph subscription endpoints (Default: /ws)
      subgraphs: # Overrides subscription settings for individual subgraphs
        reviews: # Overrides settings for the 'reviews' subgraph
          path: /ws # Absolute path that overrides the preceding '/subscriptions' path for 'all'
          protocol: graphql_ws # The WebSocket-based subprotocol to use for subscription communication (Default: graphql_ws)
          heartbeat_interval: 10s # Optional and 'disable' by default, also supports 'enable' (set 5s interval) and custom values for intervals, e.g. '100ms', '10s', '1m'.
```

This example enables subscriptions in **passthrough mode**, which uses long-lived WebSocket connections.

<Note>

- Each `path` must be set as an absolute path. For example, given `http://localhost:8080/foo/bar/graphql/ws`, set the path configuration as `path: "/foo/bar/graphql/ws"`.
- Subgraph path configurations override the path configuration for `all` subgraphs.
- If your subgraph implementation (e.g. [DGS](https://netflix.github.io/dgs/)) can close idle connections, set `heartbeat_interval` to keep the connection alive.

</Note>

The router supports the following WebSocket subprotocols, specified via the `protocol` option:

- `graphql_ws`
  - Used by the [graphql-ws](https://github.com/enisdenjo/graphql-ws) library
  - This subprotocol is the **default value** and is recommended for GraphQL server libraries implementing WebSocket-based subscriptions.
- `graphql_transport_ws`
  - Legacy subprotocol used by the [`subscriptions-transport-ws` library](https://github.com/apollographql/subscriptions-transport-ws), which is **unmaintained** but provided for backward compatibility.

By default, the router uses the `graphql_ws` protocol option for all subgraphs. You can change this global default and/or override it for individual subgraphs by setting the `protocol` key as shown above.

Your router creates a separate WebSocket connection for each client subscription, unless it can perform [subscription deduplication](#subscription-deduplication).

### HTTP callback setup

<Note>

- Your router must use whichever subprotocol is expected by each of your subgraphs.
- To disambiguate between `graph-ws` and `graph_ws`:
  - `graph-ws` (with a hyphen `-`) is the name of the [library](https://github.com/enisdenjo/graphql-ws) that uses the recommended `graphql_ws` (with un underscore `_`) WebSocket subprotocol.
- Each `path` must be set as an absolute path. For example, given `http://localhost:8080/foo/bar/graphql/ws`, set the absolute path as `path: "/foo/bar/graphql/ws"`.
- The `public_url` must include the configured `path` on the router. For example, given a server URL of `http://localhost:8080` and the router's `path` = `/my_callback`, then your `public_url` must append the `path` to the server: `http://localhost:8080/my_callback`.
- If you have a proxy in front of the router that redirects queries to the `path` configured in the router, you can specify another path for the `public_url`, for example `http://localhost:8080/external_path`.
- Given a `public_url`, the router appends a subscription id to the `public_url` to get {`http://localhost:8080/external_access/{subscription_id}`} then passes it directly to your subgraphs.
- If you don't specify the `path`, its default value is `/callback`, so you'll have to specify it in `public_url`.

</Note>

The router provides support for receiving subgraph subscription events via HTTP callbacks, instead of over a persistent WebSocket connection. This **callback mode** provides the following advantages over WebSocket-based subscriptions:

- The router doesn't need to maintain a persistent connection for each distinct subscription.
- You can publish events directly to the router from a pubsub system, instead of routing those events through the subgraph.

Callback mode requires your subgraph library to support the router's [HTTP callback protocol](/router/executing-operations/subscription-callback-protocol/).

<Note>

Currently, [Apollo Server 4.10](https://github.com/apollographql/apollo-server/releases/tag/%40apollo%2Fserver%404.10.0) and [Spring GraphQL 4.3.0](https://github.com/apollographql/federation-jvm/tree/main/spring-subscription-callback#usage) support this protocol. If you're implementing support in a subgraph library, please [create a GitHub discussion](https://github.com/apollographql/router/discussions/).

</Note>

Here's an example configuration that sets up subgraph subscriptions in callback mode:

```yaml title="router.yaml"
subscription:
  enabled: true
  mode:
    callback:
      public_url: https://example.com:4000/callback # The router's public URL, which your subgraphs access, must include the path configured on the router
      listen: 127.0.0.1:4000 # The IP address and port the router will listen on for subscription callbacks, for security reasons it might be better to expose on another port that is only available from your internal network
      path: /callback # The path of the router's callback endpoint
      heartbeat_interval: 5s # Optional (default: 5secs)
      subgraphs: # The list of subgraphs that use the HTTP callback protocol
        - accounts
```

You can disable the heartbeat by setting `heartbeat_interval: disabled`. This is useful for example if you're running in callback mode in an infrastructure based on lambda functions, where you prefer neither to send heartbeats nor to keep a lambda awake just to send heartbeats to subscriptions.

<Caution>

Once the heartbeat is disabled, you must manage how a subscription is closed from the server side.

If something crashes on your side for a specific subscription on specific events, but you don't manage closing the subscription, you can end up with a subscription still opened on the client but without any events to notify you on the client side that the subscription has crashed.

Also, when handling subscriptions with heartbeats disabled, make sure to store a subscription's request payload (including extensions data with callback URL and verifier) to be able to send the right events on the right callback URL when you start your lambda function triggered by an event.

</Caution>

### Using a combination of modes

If some of your subgraphs require [passthrough mode](#websocket-setup) and others require [callback mode](#http-callback-setup) for subscriptions, you can apply different modes to different subgraphs in your configuration:

```yaml title="router.yaml"
subscription:
  enabled: true
  mode:
    passthrough:
      subgraphs:
        reviews: #highlight-line
          path: /ws
          protocol: graphql_ws
    callback:
      public_url: http://public_url_of_my_router_instance:4000/callback # This must include the path configured on the router
      listen: 127.0.0.1:4000
      path: /callback
      subgraphs:
        - accounts #highlight-line
```

In this example, the `reviews` subgraph uses WebSocket and the `accounts` subgraph uses HTTP-based callbacks.

<Caution>

If you configure both passthrough mode and callback mode for a particular subgraph, the router uses the passthrough mode configuration.

If any subgraphs require callback mode, **do not set the `passthrough.all` key**. If you do, the router uses the passthrough mode configuration for all subgraphs.

</Caution>

## Example execution

Let's say our supergraph includes the following subgraphs and partial schemas:

<CodeColumns>

```graphql title="Products subgraph"
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Int!
}

# highlight-start
type Subscription {
  productPriceChanged: Product!
}
#highlight-end
```

```graphql title="Reviews subgraph"
type Product @key(fields: "id") {
  id: ID!
  reviews: [Review!]!
}

type Review {
  score: Int!
}
```

</CodeColumns>

Now, let's say a client executes the following subscription against our router ([over HTTP](#how-it-works)):

```graphql
subscription OnProductPriceChanged {
  productPriceChanged {
    # Defined in Products subgraph
    name
    price
    reviews {
      # Defined in Reviews subgraph
      score
    }
  }
}
```

When our router receives this operation, it executes a corresponding subscription operation against the Products subgraph (over a new WebSocket connection):

```graphql
subscription {
  productPriceChanged {
    id # Added for entity fetching
    name
    price
    # Reviews fields removed
  }
}
```

<Note>

- This operation adds the `Product.id` field. The router needs `@key` fields of the `Product` entity to merge entity fields from across subgraphs.
- This operation removes all fields defined in the Reviews subgraph, because the Products subgraph can't resolve them.

</Note>

At any point after the subscription is initiated, the Products subgraph might send updated data to our router. Whenever this happens, the router does not immediately return this data to the client, because it's missing requested fields from the Reviews subgraph.

Instead, our router executes a standard GraphQL query against the Reviews subgraph to fetch the missing entity fields:

```graphql
query {
  entities(representations: [...]) {
    ... on Product {
      reviews {
        score
      }
    }
  }
}
```

After receiving this query result from the Reviews subgraph, our router combines it with the data from Products and returns the combination to the subscribing client.

## Trying subscriptions with `curl`

To quickly try out the GraphOS router's HTTP-based subscriptions without setting up an Apollo Client library, you can execute a `curl` command against your router with the following format:

```bash
 curl 'http://localhost:4000/' -v \
  -H 'accept: multipart/mixed;subscriptionSpec=1.0, application/json' \
  -H 'content-type: application/json' \
  --data-raw '{"query":"subscription OnProductPriceChanged { productPriceChanged { name price reviews { score } } }","operationName":"OnProductPriceChanged"}'
```

This command creates an HTTP multipart request and keeps an open connection that receives new subscription data in response "chunks":

```
--graphql
content-type: application/json

{}
--graphql
content-type: application/json

{"payload":{"data":{"productPriceChanged":{"name":"Croissant","price":400,"reviews":[{"score":5}]}}}}
--graphql
content-type: application/json

{"payload":{"data":{"productPriceChanged":{"name":"Croissant","price":375,"reviews":[{"score":5}]}}}}
--graphql
content-type: application/json

{"payload":{"data":{"productPriceChanged":{"name":"Croissant","price":425,"reviews":[{"score":5}]}}}}
--graphql--
```

This example subscription only emits three events and then directly closes the connection.

For more information on this multipart HTTP subscription protocol, see [this article](/router/executing-operations/subscription-multipart-protocol/).

## Subscription deduplication

**By default, the router deduplicates identical subscriptions.** This can dramatically reduce load on both your router and your subgraphs, because the router doesn't need to open a new connection if an existing connection is already handling the exact same subscription.

For example, if thousands of clients all subscribe to real-time score updates for the same sports game, your router only needs to maintain one connection to your `sportsgames` subgraph to receive events for all of those subscriptions.

The router considers subscription operations **identical** if all of the following are true:

- The operations sent to the subgraph have identical GraphQL selection sets (i.e., requested fields).
- The operations provide identical values for all headers that the router sends to the subgraph, except those listed in `ignored_headers`.

### Configuring deduplication

You can ignore specific headers when deduplicating subscriptions.

If you propagate a header with varying values to your subgraph (for example, `User-Agent`), but you still want to deduplicate subscriptions and you can accept that only one out of many values will be propagated, you can configure `ignored_headers` like this:

```yaml title="router.yaml"
subscription:
  enabled: true
# highlight-start
  deduplication:
    enabled: true # default: true
    ignored_headers:
      - user-agent # It won't include this header in the deduplication algorithm, so even if the value is different it will still deduplicate
# highlight-end
```

### Disabling deduplication

You can disable subscription deduplication by adding the following to your router's YAML config file under the `subscription` key:

```yaml title="router.yaml"
subscription:
  enabled: true
# highlight-start
  deduplication:
    enabled: false # default: true
# highlight-end
```

Note that this is a global setting (not per-subgraph or per-operation).

#### Why disable deduplication?

Disabling deduplication is useful if you need to create a separate connection to your subgraph for each client-initiated subscription. For example:

- Your subgraph needs to trigger an important event every time a new client subscribes to its data.
    - This event doesn't trigger whenever the router reuses an existing connection.
- Your subscription needs to start by receiving the first value in a particular sequence, instead of the most recent value.
    - If a subscription reuses an existing connection, it starts by receiving the next value for that connection.
    - As a basic example, let's say a subscription should always fire events returning the integers `0` through `1000`, in order. If a new subscription reuses an existing subgraph connection, it starts by receiving whichever value is next for the original connection, which is almost definitely not `0`.

## Advanced configuration

### Termination on schema update

Whenever your router's supergraph schema or configuration is updated, **the router terminates all active subscriptions.** Clients can detect this special-case termination via an error code and establish a new subscription.

Subscriptions are terminated in the following cases:

- Your router regularly polls GraphOS for its supergraph schema, and an updated schema becomes available.
- Your router obtains its supergraph schema or configuration from a local file, which it watches for updates if the [`--hot-reload` option](/router/configuration/overview#--hr----hot-reload) is set.

The router then sends the following as a final response payload to all active subscribing clients:

```json
{
  "errors": [
    {
      "message": "subscription has been closed due to a schema reload",
      "extensions": {
        "code": "SUBSCRIPTION_SCHEMA_RELOAD"
      }
    }
  ]
}
```

A client that receives this `SUBSCRIPTION_SCHEMA_RELOAD` or `SUBSCRIPTION_CONFIG_RELOAD` error code can reconnect by executing a new subscription operation.

### WebSocket auth support

By default, if you've configured your router to [propagate](/graphos/routing/header-propagation/) HTTP `Authorization` headers to your subgraph, then the router automatically sets corresponding `connectionParams` when initiating a WebSocket connection to that subgraph.

For example, when your router sends the [`connection_init` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#connectioninit) to a subgraph, it includes the value of the `Authorization` header via the following payload:

```json
{
  "connectionParams": {
    "token": "CONTENTS_OF_AUTHORIZATION_HEADER"
  }
}
```

To specify a custom payload for  the`connection_init` message, you can write a [Rhai script](/graphos/routing/customization/rhai/) and use the `context` directly:

```rhai
fn subgraph_service(service, subgraph) {
  let params = Router.APOLLO_SUBSCRIPTION_WS_CUSTOM_CONNECTION_PARAMS;
  let f = |request| {
    request.context[params] = #{
			my_token: "here is my token"
		};
  };

  service.map_request(f);
}
```

<Note>

If you specify both a `context` entry and an `Authorization` header, the `context` entry takes precedence.

</Note>

### Expanding event queue capacity

If your router receives a high volume of events for a particular subscription, it might accumulate a backlog of those events to send to clients. To handle this backlog, the router maintains an in-memory queue of unsent events.

The router maintains a separate event queue for each of its active subscription connections to subgraphs.

You can configure the size of each event queue in your router's YAML config file, like so:

```yaml title="router.yaml"
subscription:
  enabled: true
  queue_capacity: 100000 # Default: 128
```

The value of `queue_capacity` corresponds to the maximum number of subscription events for each queue, not the total size of those events.

Whenever your router receives a subscription event when its queue is full, it discards the oldest unsent event in the queue and enqueues the newly received event. The discarded event is not sent to subscribing clients.

If it's absolutely necessary for clients to receive every subscription event, increase the size of your event queue as needed.

### Limiting the number of client connections

Client subscriptions are [long-lived HTTP connections](#how-it-works), which means they might remain open indefinitely. You can limit the number of simultaneous client subscription connections in your router's YAML config file, like so:

```yaml title="router.yaml"
subscription:
  enabled: true
  #highlight-start
  max_opened_subscriptions: 150 # Only 150 simultaneous connections allowed
  #highlight-end
```

If a client attempts to execute a subscription on your router when it's already at `max_open_subscriptions`, the router rejects the client's request with an error.
